/*
 * VMWIFI is a host tool delegating virtual machine wifi specific operations,
 * helping guest to touch the real wifi, now only "probe request"(maybe the only?).
 *
 * It listens to the virtual bridge where guest linked. Guest requests wifi
 * operations by sending specific ethernet packets. Then VMWIFI transmits it
 * to host nl80211 module, collects results and responds back.
 *
 * VMWIFI depends on package libnl-genl-3-dev.
 *
 * Basic code tutored from Robpol86 and 小菜学编程. Thanks.
 *   https://github.com/Robpol86/libnl/blob/master/example_c/
 *   https://github.com/coding-fans/linux-network-programming/blob/master/src/c/ethernet/
 *
 * Copyright (c) laokz <laokz@foxmail.com>
 * VMWIFI is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
#include <stdio.h>
#include <stdint.h>
#include <netlink/netlink.h>
#include <netlink/genl/genl.h>
#include <linux/nl80211.h>
#include <linux/if_ether.h>
#include "vmwifi.h"

/*
 * OHOS guest expected data, from hdf_wifi_event.h
 *
 * When callback determin to transfer these data to guest, it is important
 * to use canonical way to do serialization, so that the guest can
 * understand and retrieve the data correctly.
 */
struct ProbeResp {
    uint64_t timestamp;
    uint16_t beaconInt;
    uint16_t capabInfo;
    uint8_t variable[0];
};
struct Ieee80211Mgmt {
    uint16_t frameControl;
    uint16_t duration;
    uint8_t dstAddr[6];
    uint8_t srcAddr[6];
    uint8_t bssid[6];
    uint16_t seqCtrl;
    union {
        struct ProbeResp probeResp;
    } u;
};
struct ScannedBssInfo {
    int32_t                  signal;
    int16_t                  freq;
    uint8_t                  array[2];
    uint32_t                 mgmtLen;
    /*
     * If host is 64bit arch, the pointer size and alignment will conflict
     * with 32bit guest in data layout. It's trivial that we can alter the
     * pointer type to int32_t, because its value must be assigned at guest
     * side.
     * For `mgmt` is the last member, we just give it some compile attributes.
     */
    struct Ieee80211Mgmt    *mgmt __attribute__ ((packed, aligned (4)));
};

/*
 * nl_recvmsg_msg_cb_t, deal with every message nl80211 feedback, if needed
 * send back information to guest.
 *
 * For simplicity, here serialization is just plain memory operation, but
 * take care of alignment, pointer size, etc.
 */
int GetScanResultCb(struct nl_msg *msg, void *arg)
{
    /* response ethernet frame payload to guest */
    char data[ETH_DATA_LEN];
    int len;
    /* payload first 2 bytes leave to `VwCodeT` code */
    struct ScannedBssInfo *bi = (void *)data + sizeof(short);
    /* `mg` just following `bi`, its address is fit for 32bit guest */
    struct Ieee80211Mgmt *mg = (void *)&bi->mgmt + sizeof(int32_t);
    struct nlattr *bss[NL80211_BSS_MAX + 1];
    int ret;

    do {
        struct nlmsghdr * nlh = nlmsg_hdr(msg);
        struct nlattr *tb[NL80211_ATTR_MAX + 1];
        if ((ret = genlmsg_parse(nlh, 0, tb, NL80211_ATTR_MAX, NULL)) < 0) {
            fprintf(stderr, "%s:%d %s\n", __func__, __LINE__, nl_geterror(ret));
            goto OUT;
        }

        if ((ret = nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], NULL)) <0) {
            fprintf(stderr, "%s:%d %s\n", __func__, __LINE__, nl_geterror(ret));
            goto OUT;
        }

        mg->seqCtrl = nla_get_u32(tb[NL80211_ATTR_GENERATION]);
    } while (0);

    bi->signal = nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM]);  /* mBm */
    bi->freq = nla_get_u32(bss[NL80211_BSS_FREQUENCY]);     /* MHz */

    char *p = nla_data(bss[NL80211_BSS_BSSID]);
    for (int i = 0; i < nla_len(bss[NL80211_BSS_BSSID]) && i < 6; i++) {
        mg->bssid[i] = p[i];
    }

    mg->u.probeResp.beaconInt = nla_get_u32(bss[NL80211_BSS_BEACON_INTERVAL]);
    mg->u.probeResp.capabInfo = nla_get_u32(bss[NL80211_BSS_CAPABILITY]);

    if (bss[NL80211_BSS_LAST_SEEN_BOOTTIME]) {
        mg->u.probeResp.timestamp = nla_get_u64(bss[NL80211_BSS_LAST_SEEN_BOOTTIME]);   /* ns */
    }

    len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
    memcpy(mg->u.probeResp.variable, nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]), len);
    bi->mgmtLen = mg->u.probeResp.variable - (uint8_t *)mg + len;

    p = (uint8_t *)mg + len;
    len = p - data;

    *(short *)data = VW_CODE_SCAN_ALL;
    VwSendResponse(nla_data(bss[NL80211_BSS_BSSID]), data, len);
    return NL_OK;

OUT:/* fatal error, stop remaining transact of this request */
    return NL_STOP;
}

int main()
{
    int ret, req;

    ret = VwInit(NULL);
    if (ret < 0) {
        return 1;
    }

    while ((req = VwWaitRequest())) {
        switch (req) {
            case VW_CODE_INVALID:
                break;  /* ignore and retry */
            case VW_CODE_SCAN_ALL:
                ret = VwDoScan(GetScanResultCb, NULL);
                if (ret < 0) {
                    goto OUT;
                }
                break;
            default:
                ret = 1;
                goto OUT;
        }
    }

OUT:
    VwDeInit();
    return ret;
}
